/* @flow */

import {
  warn,
  remove,
  isObject,
  parsePath,
  _Set as Set,
  handleError,
  noop
} from '../util/index'

import { traverse } from './traverse'
import { queueWatcher } from './scheduler'
import Dep, { pushTarget, popTarget } from './dep'

import type { SimpleSet } from '../util/index'

let uid = 0

/**
 * A watcher parses an expression, collects dependencies,
 * and fires callback when the expression value changes.
 * This is used for both the $watch() api and directives.
 * 包括渲染watcher、计算属性watcher、用户自定义watcher
 */
export default class Watcher {
  vm: Component;
  expression: string;
  cb: Function;
  id: number;
  deep: boolean;
  user: boolean;
  lazy: boolean;
  sync: boolean;
  dirty: boolean;
  active: boolean;
  deps: Array<Dep>;
  newDeps: Array<Dep>;
  depIds: SimpleSet;
  newDepIds: SimpleSet;
  before: ?Function;
  getter: Function;
  value: any;

  constructor (
    vm: Component, // 组件实例
    // expOrFn：
    //   1）对于渲染watcher，就是updateComponent方法
    //   2）对于计算属性watcher，就是用户定义的computedFn，或者自定义的对象的getter函数
    //   3）对于自定义watcher，就是expOrFn(监控的那个key，后面通过parsePath将其转换为一个函数)

    expOrFn: string | Function,  
    cb: Function, // 用户watcher的callback函数
    options?: ?Object,
    isRenderWatcher?: boolean
  ) {
    this.vm = vm
    if (isRenderWatcher) {
      // vm._watcher，组件的渲染watcher
      vm._watcher = this
    }
    // vm._watchers，当前vm上的所有watcher，
    // vm._computedWatchers：存放计算属性watcher
    // vm._watcher，组件的渲染watcher

    // 存放vm上所有的watcher: 包括渲染watcher，用户自定义watcher(user为true)，computed watcher(lazy为true)
    vm._watchers.push(this)
    // options
    if (options) {
      this.deep = !!options.deep  // 是否深度监听
      this.user = !!options.user  // options.user = true,用户watcher
      this.lazy = !!options.lazy  // options.lazy = true，为计算属性watcher
      this.sync = !!options.sync
      this.before = options.before
    } else {
      this.deep = this.user = this.lazy = this.sync = false
    }
    this.cb = cb
    this.id = ++uid // uid for batching
    this.active = true
    // dirty为true时，表示computed watcher
    this.dirty = this.lazy // for lazy watchers
    this.deps = []
    this.newDeps = []
    this.depIds = new Set()
    this.newDepIds = new Set()
    this.expression = process.env.NODE_ENV !== 'production'
      ? expOrFn.toString()
      : ''
    // parse expression for getter
    // 1.针对"渲染watcher"：expOrFn是一个函数，this.getter就是updateComponent函数，cb为noop空函数，options中定义了beforeUpdate钩子函数
    // 2.针对"计算属性watcher"：expOrFn是计算属性key所对应的那个函数，cb为noop空函数，options中lazy为true
    if (typeof expOrFn === 'function') {
      this.getter = expOrFn
    } else {
      // 3. 针对"用户watcher(即$watch)"，expOrFn就是一个表达式(data中某一个属性)，通常为watch选项的key。
      // 例如watch: {'a': fn, 'b.c.d': fn2 }，其中'a'、'b.c.d'就是expOrFn
      this.getter = parsePath(expOrFn)
      if (!this.getter) {
        this.getter = noop
        process.env.NODE_ENV !== 'production' && warn(
          `Failed watching path: "${expOrFn}" ` +
          'Watcher only accepts simple dot-delimited paths. ' +
          'For full control, use a function instead.',
          vm
        )
      }
    }
    // lazy为true(计算属性watcher)，默认不执行getter方法。即：计算属性watcher，不会进入'targetStack'栈中
    this.value = this.lazy
      ? undefined
      : this.get() // 执行get方法取值
  }

  /**
   * Evaluate the getter, and re-collect dependencies.
   */
  get () {
    // 全局的Dep.target = 当前渲染watcher, 或者用户watcher
    // watcher入栈
    pushTarget(this)
    let value
    const vm = this.vm
    try {
      // 调用getter方法 --> updateComponent -> vm._update(vm._render)，对vm上的key取值，触发defineReactive的get方法，进行依赖收集。
      // 此时全局的Dep.target = this,即当前的watcher
      // value是老的值,
      // 1、对渲染watcher而言，this.getter执行时，就会执行updateComponent方法-> vm._update(vm._render())，取值并生成vnode。此时每个key的dep就会收集“渲染watcher这个依赖”
      // 2、对用户watcher而言，this.getter执行时，会去vm上取对应watch的那个key的值，然后这个key的dep就会收集"用户wather"这个依赖。
      // 3、对计算属性wather而言，this.getter，会去执行key对应的函数（这个函数内部会去取vm上的其他key值，其他key的dep就会收集计算属性watcher这个依赖）
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)
      } else {
        throw e
      }
    } finally {
      // "touch" every property so they are all tracked as
      // dependencies for deep watching
      // 如果deep为true，表示当前为用户watcher，此时的this.getter就是我们求表达式值的方法(parsePath(expOrFn))，value就是我们watch的那个key的值.
      // 深度监听时，调用traverse方法，它会遍历value对象的每一个key并取值，触发其get方法，进行依赖收集，
      // 即：将对象的每一个key对应的dep添加到当前watcher的dep数组中，同时每一个dep的subs中添加当前watcher
      if (this.deep) {
        traverse(value)
      }
      // watcher出栈
      popTarget()
      // 清理deps，将newDeps赋值给deps，newDepIds赋值给depIds。同时清空depIds、deps
      /** 清理deps的原因是：
      考虑到一种场景，我们的模板会根据 v -if 去渲染不同子模板 a 和 b，
      当我们满足某种条件的时候渲染 a 的时候，会访问到 a 中的数据，这时候我们对 a 使用的数据添加了 getter，做了依赖收集，那么当我们去修改 a 的数据的时候，理应通知到这些订阅者。
      那么如果我们一旦改变了条件渲染了 b 模板，又会对 b 使用的数据添加了 getter，
      如果我们没有依赖移除的过程，那么这时候我去修改 a 模板的数据，会通知 a 数据的订阅的回调，这显然是有浪费的。

      因此 Vue 设计了在每次添加完新的订阅，会移除掉旧的订阅，这样就保证了在我们刚才的场景中，如果渲染 b 模板的时候去修改 a 模板的数据，a 数据订阅回调已经被移除了，所以不会有任何浪费，
       */
      this.cleanupDeps()
    }
    return value
  }

  /**
   * Add a dependency to this directive.
   */
  addDep (dep: Dep) {
    const id = dep.id
    if (!this.newDepIds.has(id)) {
      // watcher记录dep(用的是newDeps)
      this.newDepIds.add(id)
      this.newDeps.push(dep)
      if (!this.depIds.has(id)) {
        // dep记录watcher
        dep.addSub(this)
      }
    }
  }

  /**
   * Clean up for dependency collection.
   */
  cleanupDeps () {
    let i = this.deps.length
    while (i--) {
      const dep = this.deps[i]
      if (!this.newDepIds.has(dep.id)) {
        // 从dep中移除无关的watcher
        dep.removeSub(this)
      }
    }
    let tmp = this.depIds
    this.depIds = this.newDepIds
    this.newDepIds = tmp
    this.newDepIds.clear()
    tmp = this.deps
    this.deps = this.newDeps
    this.newDeps = tmp
    this.newDeps.length = 0
  }

  /**
   * Subscriber interface. Will be called when a dependency changes.
   */
  update () {
    /* istanbul ignore else */
    if (this.lazy) {  
      // update时，如果是计算属性watcher，设置watcher的dirty为true, 之后重新渲染页面的时候(渲染watcher)，就可以重新取值(dirty为true时，计算属性watcher执行evaluate求值)
      this.dirty = true
    } else if (this.sync) { // todo: 同步watcher(非异步渲染视图)，在vuex中使用到
      this.run()
    } else {
      queueWatcher(this)
    }
  }

  /**
   * Scheduler job interface.
   * Will be called by the scheduler.
   */
  run () {
    if (this.active) {
      // 获取新的值
      const value = this.get()
      if (
        value !== this.value ||
        // Deep watchers and watchers on Object/Arrays should fire even when the value is the same, because the value may have mutated.
        // 当deep为true，并且监控的value是一个对象/数组，cb需要触发。因为这个value值可能已经变了。
        isObject(value) ||
        this.deep
      ) {
        // set new value
        const oldValue = this.value;
        // 更新老值
        this.value = value;
        if (this.user) { // 用户watcher，就是常用的$watch(或者watch选项)
          try {
            // 当用户watch的值发生变化(或者value为对象/数组)，执行cb方法
            this.cb.call(this.vm, value, oldValue)
          } catch (e) {
            handleError(e, this.vm, `callback for watcher "${this.expression}"`)
          }
        } else {
          this.cb.call(this.vm, value, oldValue)
        }
      }
    }
  }

  /**
   * Evaluate the value of the watcher.
   * This only gets called for lazy watchers.
   */
  evaluate() {
    // 计算属性watcher计算完成后，设置dirty为false，以后只要依赖的dep的值没有发生变化，执行计算属性watcher时，就不会重新求值
    this.value = this.get()
    this.dirty = false
  }

  /**
   * Depend on all deps collected by this watcher.
   * 循环watcher里的dep，执行depend方法，进行依赖收集(收集targetStack中的watcher)
   */
  depend() {
    // watcher里存dep，主要用于计算属性watcher（目的是：计算属性watcher中的dep，可以收集渲染watcher。这样的话，当dep对应的数据变化时，页面可以重新渲染）
    let i = this.deps.length
    while (i--) {
      this.deps[i].depend();  // 计算属性watcher中的dep，去收集渲染watcher
    }
  }

  /**
   * Remove self from all dependencies' subscriber list.
   */
  teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this); // 1、从watcher列表中删除自身
      }
      let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this); // 2、从当前watcher的dep中，删除对应dep中存储的watcher
      }
      this.active = false
    }
  }
}
